package common;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import common.data.MessageTypeEnum;
import common.data.RequestTypeEnum;
import common.data.ResponseTypeEnum;

public class SerializationManager {
	/**
	 * SerializationManager is a helper that serialize
	 * primitive data, flatten object attributes.
	 */
	
	/*
	 * Singleton helper
	 */
	private static SerializationManager current;

	public static SerializationManager getCurrent() {
		if (current == null) {
			current = new SerializationManager();
		}
		return current;
	}

	/*
	 * Primitive data serialization
	 */
	public byte[] fromFloat(float data) {
		return this.fromInt(Float.floatToRawIntBits(data));
	}

	public byte[] fromInt(int data) {
		return new byte[] { (byte) (data & 0xFF),
				(byte) ((data >> 8) & 0xFF), (byte) ((data >> 16) & 0xFF),
				(byte) ((data >> 24) & 0xFF), };
	}

	public byte[] fromLong(long data) {
		return new byte[] { (byte) ((data) & 0xFF),
				(byte) ((data >> 8) & 0xFF), (byte) ((data >> 16) & 0xFF),
				(byte) ((data >> 24) & 0xFF), (byte) ((data >> 32) & 0xFF),
				(byte) ((data >> 40) & 0xFF), (byte) ((data >> 48) & 0xFF),
				(byte) ((data >> 56) & 0xFF), };
	}

	public byte[] fromChar(char data) {
		return new byte[] { (byte) ((data) & 0xFF)};
	}

	public byte[] fromBoolean(boolean data){
		return new byte[]{ (byte)(data ? 0x01 : 0x00) };
	}
	/*
	 * String serialization
	 */
	public byte[] fromString(String str) {
		byte[] result = new byte[str.length() * Constants.CHAR_LENGTH];
		for (int i = 0; i < str.length(); i++) {
			System.arraycopy(this.fromChar(str.charAt(i)), 0, result, i
					* Constants.CHAR_LENGTH, Constants.CHAR_LENGTH);
		}
		return result;
	}

	/*
	 * Enum serialization
	 */
	public byte[] fromCurrencyEnum(CurrencyEnum currencyEnum) {
		int currency = currencyEnum.ordinal();
		return this.fromInt(currency);
	}

	public byte[] fromMessageTypeEnum(MessageTypeEnum messageTypeEnum) {
		return this.fromInt(messageTypeEnum.ordinal());
	}

	public byte[] fromRequestTypeEnum(RequestTypeEnum messageTypeEnum) {
		return this.fromInt(messageTypeEnum.ordinal());
	}
	
	public byte[] fromResponseTypeEnum(ResponseTypeEnum responseTypeEnum){
		return this.fromInt(responseTypeEnum.ordinal());
	}
	
	/*
	 * Object serialization
	 */
	//serialize a field into a byte stream of field length and field data
	public byte[] fromField(Field field, Object data) 
			throws IllegalArgumentException, IllegalAccessException{
		DataTypeEnum fieldType = DataTypeEnum.getDataTypeEnumByName(field.getType().getSimpleName());
		
		byte[] fieldData = null;
		
		switch(fieldType){
		case BOOLEAN:
			fieldData = this.fromBoolean(field.getBoolean(data));
			break;
		case BYTE:
			fieldData = new byte[] { field.getByte(data) };
			break;
		case CHAR:
			fieldData = this.fromChar(field.getChar(data));
			break;
		case CURRENCY_ENUM:
			fieldData = this.fromInt(((CurrencyEnum) field.get(data)).ordinal()); 
			break;
		case FLOAT:
			fieldData = this.fromFloat(field.getFloat(data));
			break;
		case INTEGER:
			fieldData = this.fromInt(field.getInt(data));
			break;
		case LONG:
			fieldData = this.fromLong(field.getLong(data));
			break;
		case MESSAGE_TYPE_ENUM:
			fieldData = this.fromInt(((MessageTypeEnum) field.get(data)).ordinal());
			break;
		case REQUEST_TYPE_ENUM:
			fieldData = this.fromInt(((RequestTypeEnum) field.get(data)).ordinal());
			break;
		case STRING:
			fieldData = this.fromString((String) field.get(data));
			break;
		}
		
		byte[] fieldLength = this.fromInt(fieldData.length);

		byte[] result = new byte[fieldLength.length + fieldData.length];
		System.arraycopy(fieldLength, 0, result, 0, fieldLength.length);
		System.arraycopy(fieldData, 0, result, fieldLength.length, fieldData.length);
		return result;
	}
	
	public byte[] fromObject(Object object) throws IllegalArgumentException, IllegalAccessException{
        List<byte[]> fieldByteList = new ArrayList<>();
        
        int length = 0;
        for (Field field: object.getClass().getSuperclass().getDeclaredFields()) {
        	byte[] fieldByte = this.fromField(field, object);
        	fieldByteList.add(fieldByte);
        	length += fieldByte.length;
        }

        int currentIndex = 0;
        byte[] result = new byte[length];
        for (byte[] fieldByte: fieldByteList){
        	System.arraycopy(fieldByte, 0, result, currentIndex, fieldByte.length);
        	currentIndex += fieldByte.length;
        }
        return result;
	}
}
